// session 管理模块
#ifndef __SESSION__HPP
#define __SESSION__HPP

#include "Log.hpp"
#include "util.hpp"

typedef enum
{
    LOGIN,  /*登录状态*/
    UNLOGIN /*未登录状态*/
} session_status;

/*会话 - 提供基础的设置获取功能*/
class Session
{
public:
    Session(uint64_t ssid)
        : _ssid(ssid)
    {
        DBGLOG("会话 %p 被创建 !", this);
    }

    ~Session()
    {
        DBGLOG("会话 %p 被销毁 !", this);
    }

    void SetStatus(const session_status status) { _status = status; }
    void SetUserID(const uint64_t uid) { _uid = uid; }
    /*设置定时器 , 超时 session 自动销毁 , 即 : 过期时间*/
    void SetTimerPtr(const wsserver_t::timer_ptr &tp) { _tp = tp; }
    uint64_t GetUserID() { return _uid; }
    uint64_t GetSessionID() { return _ssid; }
    session_status GetStatus() { return _status; }
    /*获取定时器指针*/
    wsserver_t::timer_ptr &GetTimerPtr() { return _tp; }

    bool IsLogin() { return _status == LOGIN; }

private:
    uint64_t _ssid;            /*会话ID*/
    session_status _status;    /*会话状态*/
    uint64_t _uid;             /*会话对应的用户*/
    wsserver_t::timer_ptr _tp; /*用于设置回话的过期时间 ,需要websocketpp 中的 set_timer , set_timer 被封装*/
};

/*会话管理 - 完成对会话的生命周期的管理*/
/*ms 值 : */
#define SESSION_TIMEOUT 30000 /*过期时间 , session 过期自动删除 , 超过30s*/
#define SESSION_LASTING -1    /*session 永久存在*/
using session_ptr = std::shared_ptr<Session>;

class SessionManage
{

private:
    bool IsInSessionManage(uint64_t ssid)
    {
        auto ret = _ssid_session.find(ssid);
        if (ret == _ssid_session.end())
        {
            ERRLOG("%lu session is not exists !", ssid);
            return false;
        }
        return true;
    }

public:
    SessionManage(wsserver_t *server)
        : _auto_ssid(1),
          _server(server)
    {
        DBGLOG("session 管理器初始化成功 !");
    }
    ~SessionManage()
    {
        DBGLOG("session 管理器销毁成功 !");
    }

    /*创建会话*/
    session_ptr CreateSession(uint64_t uid, session_status status)
    {
        session_ptr sptr = nullptr;
        /*保证线程安全*/
        {
            std::unique_lock<std::mutex> lock(_mutex);
            /*1 . 创建 session , 并分配会话ID*/
            sptr = std::make_shared<Session>(_auto_ssid);
            /*2 . 设置会话信息*/
            sptr->SetStatus(status);
            sptr->SetUserID(uid);
            /*过期时间有专门函数设定*/
            /*3 . 将该 session 添加到管理列表*/
            _ssid_session.insert(std::make_pair(_auto_ssid, sptr));
            /*4 . 继续为 session 分配ID , 保证分配ID原子性*/
            _auto_ssid++;
        }

        /*5. 返回会话*/
        return sptr;
    }
    /*销毁会话*/
    void DestorySession(uint64_t ssid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        if (!IsInSessionManage(ssid))
        {
            return;
        }
        _ssid_session.erase(ssid);
    }
    /*通过会话ID获取会话*/
    session_ptr GetSession(uint64_t ssid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        if (!IsInSessionManage(ssid))
        {
            return session_ptr();
        }
        /*存在 , 获取*/
        auto ret = _ssid_session.find(ssid);
        return ret->second;
    }

    void AddSession(const session_ptr &ss_ptr)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _ssid_session.insert(std::make_pair(ss_ptr->GetSessionID(), ss_ptr));
    }

    /*设置 session 过期时间*/
    void SetSessionExpireTime(uint64_t ssid, int ms) /*ms : 毫秒*/
    {
        // 过期时间的设置是根据 websocket 中的 set_timer , 分为几种情况 :
        // 1. 用户登录 , 与服务器建立的短连接 , 所以 session 不能永久存在
        // 2. 一旦用户进入游戏大厅/房间 , 是基于 websocket 的长连接 , session 要保持 , 永久存在
        // 3. 用户退出房间/大厅 , 要设置为无长时间通信 , session 过期
        // 所以 , session 的持久性是要变化的 !

        /*1. 通过 ssid 获取 session 会话*/
        session_ptr ssion_ptr = GetSession(ssid);
        if (ssion_ptr.get() == nullptr)
        {
            DBGLOG("get session [%lu] failed !", ssid);
            return;
        }

        /*2. 获取会话中的 time_ptr , 准备设置定时*/
        wsserver_t::timer_ptr time_ptr = ssion_ptr->GetTimerPtr();
        // 定时器指针
        wsserver_t::timer_ptr tp = nullptr;

        /*如果 , 该指针为空 , 证明没有设置定时器 , 没有过期时间 , 证明该会话就是好永久的 !*/
        /*3. 在 session 永久存在的情况下 , 还有设置永久存在 , 直接返回即可 */
        if (time_ptr.get() == nullptr && ms == SESSION_LASTING)
        {
            return;
        }/*4. 在 session 永久存在情况下 , 为 session 设置过期时间 -- 退出大厅/房间 .. */
        else if (time_ptr.get() == nullptr && ms != SESSION_LASTING)
        {
            /*1. 使用 websocket 设置定时 , 到时自动完成对应任务(过期自动销毁)*/
            tp = _server->set_timer(ms,
                                    std::bind(&SessionManage::DestorySession, this, ssid));
            /*2 . 为 session 会话设置定时 */
            ssion_ptr->SetTimerPtr(tp);
        } /*5. 有过期时间 , 现在需要设置 session 为永久存在 -- 登录 ..  */
        else if (time_ptr.get() != nullptr && ms == SESSION_LASTING)
        {
            /*1. 有过期时间 , 就要先取消过期时间*/
            time_ptr->cancel(); /*特别注意 : 一旦取消后 , 导致会话绑定的任务就会被立即执行 ,并且会删除会话*/
            // 并且 cancel() 也不是立即执行的 , 也是服务器执行 , 所以添加也需要设置定时器 , 不能立即添加

            /*2. 要重新添加 session , 才能设置为永久 */
            _server->set_timer(0,
                               std::bind(&SessionManage::AddSession, this, ssion_ptr)); /*重新添加*/
            ssion_ptr->SetTimerPtr(wsserver_t::timer_ptr());                            /*永久证明 time_ptr为空*/
        } /*6. 有过期时间 , 重置过期时间 -- 用户再次到来 .. */
        else if (time_ptr.get() != nullptr && ms != SESSION_LASTING)
        {
            /*1. 先取消过期时间 , 即 : 设置为永久*/
            time_ptr->cancel(); /*特别注意 : 一旦取消后 , 导致会话绑定的任务就会被立即执行 ,并且会删除会话*/
            _server->set_timer(0,
                               std::bind(&SessionManage::AddSession, this, ssion_ptr)); /*重新添加*/
            ssion_ptr->SetTimerPtr(wsserver_t::timer_ptr());                            /*为永久证明time_ptr为空*/

            /*2. 再设置过期时间*/
            tp = _server->set_timer(ms,
                                    std::bind(&SessionManage::DestorySession, this, ssid));
            /*3 . 重新为 session 设置定时 */
            ssion_ptr->SetTimerPtr(tp);
        }
    }

private:
    uint64_t _auto_ssid;                                     /*分配会话ID*/
    std::unordered_map<uint64_t, session_ptr> _ssid_session; /*建立会话ID和会话的映射 , 方便查找O(1)*/
    wsserver_t *_server;                                     /*定时器需要用该类型*/
    std::mutex _mutex;                                       /*保证线程安全*/
};

#endif

/**
 * 以上功能未测试 , 只是语法测试了 , 后续根据业务统一测试
 * 本模块实现 session 管理 , 会话很多 , 所以要统一管理生命周期
 *
 * 【难点】 :  SetExpireTime 函数的设计 -- 过期时间的设置函数
 *
 * 0 .  思想 : session 中的 wsserver_t::timer_ptr _tp 这个代表的是定时器 , 如果 : 该指针为空,没有被设置定时器(没有为 session 设置过期时间) , 如果不为空就是 , 设置了过期时间
 *
 * 1 .  分类情况 :
 * 因为 : 用户网页登录是基于 HTTP 短连接的 , 用户进入游戏大厅/房间是 websocket 通信基于长连接 , 所以会话类型不同 !
 *        所以 ,********  涉及会话状态的转换 ********
 * 所以 , 该函数的设计中会有很多情况
 * 1. 没有过期时间 , 即 : session 永久存在 , 需求要求 : session 就要永久存在 , 所以不用设置过期时间 , 直接返回 , 如 : 在游戏大厅/房间对战 ..
 * 2. 没有过期时间 , 即 : session 永久存在 , 需求要求 : session 就要设置过期时间 , 在长时间不通信时销毁会话 , 如 : 退出游戏大厅/房间 ..
 * 3. 有过期时间   , 即 : session 有超时限制,需求要求 : session 就要设置过期时间 , 在长时间不通信时销毁会话 , 如 : 登录成功后 , 进入游戏大厅/房间对战 ..
 * 4. 有过期时间   , 即 : session 有超时限制,需求要求 : session 需要重置过期时间 ,
 *                       避免用户的 session 会话超过时间后自动销毁 , 如 : 用户在超时时间内登录了 , 就要再次重新设置 , 要不然会造成用户可能在游戏中突然被中断服务器不认识了 !
 *
 * 2 .  取消过期时间时 :
 * 要注意 , cancle() 函数是 websocket 库中提供的 , 这个功能时取消 session 的过期时间 , 但是 session 在取消之前是设置了过期时间的 , 有对应的过期任务的
 *         所以 , cancle() 函数会导致 , session 的过期任务会立即执行 , 并且会销毁该 session 会话
 * 所以 , 在使用该函数时要特别注意 : 如果取消过期时间后 , 还需要设置为永久/重置过期时间 , *****  必须【先添加】session, 再设置相关内容 *****
 */
